using Crux.Core.MethodEx;
using Crux.Core.Websockets.Metadata;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Newtonsoft.Json.Linq;
using System.Reflection;

namespace Crux.Core.Websockets;
/// <summary>
/// WebSocketDispatcherOfT
/// </summary>
public class WebSocketDispatcher<THandler>(
        ILogger<WebSocketDispatcher<THandler>> logger,
        IOptions<WebSocketOptions<THandler>> options,
        IServiceProvider? provider = null)
    where THandler : WebSocketHandler
{
    #region private fields
    private readonly ILogger<WebSocketDispatcher<THandler>> _logger = logger
            ?? throw new ArgumentNullException(nameof(logger));
    private readonly IServiceProvider _provider = provider
            ?? throw new ArgumentNullException(nameof(provider));
    private readonly IOptions<WebSocketOptions<THandler>> _options = options
            ?? throw new ArgumentNullException(nameof(options));
    private readonly ConcurrentDictionary<string, DynamicMethodExecutor> _caches = new ConcurrentDictionary<string, DynamicMethodExecutor>();
    #endregion  private fields

    /// <summary>
    /// 
    /// </summary>
    /// <returns></returns>
    public THandler CreateHandler(HttpContext context)
    {
        if (_provider is not null)
            return _provider.GetRequiredService<THandler>();

        return context.RequestServices.GetRequiredService<THandler>();
    }
    /// <summary>
    /// 执行方法
    /// </summary>
    /// <param name="handler">处理器实例</param>
    /// <param name="context">websocket上下文</param>
    /// <param name="invokeMessage"></param>
    /// <returns></returns>
    public Task DispatchAsync(
        THandler handler,
        WebSocketConnectionContext context,
        InvokeMessage invokeMessage)
    {
        Requires.NotNull(handler, nameof(handler));
        Requires.NotNull(context, nameof(context));
        Requires.NotNull(invokeMessage, nameof(invokeMessage));

        try
        {
            if (!invokeMessage.CheckMethodLegitimacy())
                throw new WebsocketMethodMismatchException();

            if (!_caches.TryGetValue(invokeMessage.Target, out var executor))
            {
                var methodInfo = handler.GetType().GetMethod(invokeMessage.Target);
                if (methodInfo is null)
                {
                    throw new WebsocketMethodMismatchException();
                }

                executor = DynamicMethodExecutor.Compiled(methodInfo) ?? throw new WebsocketMethodMismatchException();

                _caches.TryAdd(invokeMessage.Target, executor);
            }

            var arguments = GenerateArguments(executor.Parameters, context, invokeMessage);

            var returnValue = executor.Execute(handler, arguments);
            if (returnValue is not null)
                return SendInvokeResultAsync(returnValue, context, invokeMessage);
            else
                return InternalSendInvokeResultAsync(context, invokeMessage);
        }
        catch (OperationCanceledException)
        {
            return Task.CompletedTask;
        }
        catch (WebsocketMethodMismatchException e)
        {
            return InternalSendInvokeResultAsync(context, invokeMessage, exception: e);
        }
        catch (InvalidCastException e)
        {
            _logger.LogError(e, e.Message);

            return InternalSendInvokeResultAsync(context, invokeMessage, exception: new WebsocketParametersMismatchException());
        }
        catch (Exception e)
        {
            _logger.LogError(e, e.Message);

            return InternalSendInvokeResultAsync(context, invokeMessage, exception: e);
        }
    }
    private Task SendInvokeResultAsync(
        object result,
        WebSocketConnectionContext context,
        InvokeMessage invokeMessage)
    {
        if (result is Task task)
        {
            return task.ContinueWith(async (_task) =>
            {
                _task.Exception?.Handle((_) => true);

                if (_task.IsFaulted)
                {
                    await InternalSendInvokeResultAsync(context, invokeMessage, exception: _task.Exception?.InnerException).ConfigureAwait(false);
                }

                if (_task.IsCompleted)
                {
                    var propertyInfo = _task.GetType().GetProperty("Result");
                    if (propertyInfo is not null)
                    {
                        var _ilGetter = DynamicGetterFactory.CreateUsingIl(propertyInfo);

                        await InternalSendInvokeResultAsync(context, invokeMessage, _ilGetter(_task)).ConfigureAwait(false);
                    }
                    else
                    {
                        await InternalSendInvokeResultAsync(context, invokeMessage).ConfigureAwait(false);
                    }
                }
            });
        }
        else
        {
            return InternalSendInvokeResultAsync(context, invokeMessage, result);
        }
    }

    static async Task InternalSendInvokeResultAsync(
        WebSocketConnectionContext context,
        InvokeMessage message,
        object? result = null,
        Exception? exception = null)
    {
        if (result is not null)
        {
            await context.SendAsync(new InvocationResponseMessage
            {
                Target = message.Target,
                InvocationId = message.InvocationId,
                Result = result
            }).ConfigureAwait(false);
        }
        else if (exception is not null)
        {
            await context.SendAsync(new ErrorInvocationMessage
            {
                Type = MessageType.InvocationResponse,
                InvocationId = message.InvocationId,
                ErrorMessage = exception.Message
            }).ConfigureAwait(false);
        }
        else
        {
            await context.SendAsync(new InvocationResponseMessage
            {
                Target = message.Target,
                InvocationId = message.InvocationId
            }).ConfigureAwait(false);
        }

    }
    object?[] GenerateArguments(
       ParameterInfo[] parameters,
       WebSocketConnectionContext conbtext,
       InvokeMessage invokeMessage)
    {
        var arguments = new object?[parameters.Length];

        for (int pointer = 0, invokeArgumentsPointer = 0;
            pointer < parameters.Length;
            pointer++)
        {
            var parameterType = parameters[pointer].ParameterType;

            if (typeof(CancellationToken) == parameterType)
            {
                arguments[pointer] = conbtext.ConnectionAborted;
            }
            else if (typeof(WebSocketConnectionContext) == parameterType)
            {
                arguments[pointer] = conbtext;
            }
            else if (invokeMessage.Arguments.Length > invokeArgumentsPointer)
            {
                // 如果是复杂类型。先序列化为json，然后由json转换回目标类型，性能慢
                if (invokeMessage.Arguments[invokeArgumentsPointer] is JObject jObject)
                {
                    arguments[pointer] = jObject.ToObject(parameterType);
                }
                // 基础类型时，直接转换即可
                else
                {
                    arguments[pointer] = Convert.ChangeType(invokeMessage.Arguments[invokeArgumentsPointer], parameterType);
                }

                invokeArgumentsPointer++;
            }
        }

        return arguments;
    }

}